// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/* Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * gc0308 dvp sensor driver code for LomboTech
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 */

#include <linux/init.h>
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/viss_isp.h>

#include <media/lombo_viss.h>

#include <mach/debug.h>
#include "../ccc.h"


static const cc_reg_t init_reg_list[] = {
	/* PCLK  24M 30fps */
	{0xfe, 0x80},
	{0xfe, 0x00},	/* set page0 */
	{0xd2, 0x10},	/* close AEC */
	{0x22, 0x55},	/* close AWB */

	{0x03, 0x01},
	{0x04, 0x2c},
	{0x5a, 0x56},
	{0x5b, 0x40},
	{0x5c, 0x4a},

	{0x22, 0x57},	/* Open AWB */

	{0x01, 0x6a},
	{0x02, 0x0c},
	{0x0f, 0x00},

	{0xe2, 0x00},	/* anti-flicker step [11:8] */
	{0xe3, 0x96},	/* anti-flicker step [7:0] */

	{0xe4, 0x01},	/* exp level 1  16.67fps */
	{0xe5, 0xc2},
	{0xe6, 0x01},	/* exp level 2  12.5fps */
	{0xe7, 0xc2},
	{0xe8, 0x01},	/* exp level 3  8.33fps */
	{0xe9, 0xc2},
	{0xea, 0x01},	/* exp level 4  4.00fps */
	{0xeb, 0xc2},

	{0xec, 0x30},

	{0x05, 0x00},
	{0x06, 0x00},
	{0x07, 0x00},
	{0x08, 0x00},
	{0x09, 0x01},
	{0x0a, 0xe8},
	{0x0b, 0x02},
	{0x0c, 0x88},
	{0x0d, 0x02},
	{0x0e, 0x02},
	{0x10, 0x22},
	{0x11, 0xfd},
	{0x12, 0x2a},
	{0x13, 0x00},

	{0x15, 0x0a},
	{0x16, 0x05},
	{0x17, 0x01},
	{0x18, 0x44},
	{0x19, 0x44},
	{0x1a, 0x1e},
	{0x1b, 0x00},
	{0x1c, 0xc1},
	{0x1d, 0x08},
	{0x1e, 0x60},
	{0x1f, 0x3F},

	{0x20, 0xff},
	{0x21, 0xf8},
	{0x22, 0x57},
	{0x24, 0xa0},	/* 0=uyvy;1=vyuy;2=yuyv;3=yvyu */
	{0x25, 0x0f},

	/* outut sync_mode */
	{0x26, 0x02},	/* modefy by tbl 20191206  default 0x03 */

	{0x2f, 0x01},
	{0x30, 0xf7},
	{0x31, 0x50},
	{0x32, 0x00},
	{0x39, 0x04},
	{0x3a, 0x18},
	{0x3b, 0x20},
	{0x3c, 0x00},
	{0x3d, 0x00},
	{0x3e, 0x00},
	{0x3f, 0x00},
	{0x50, 0x10},
	{0x53, 0x82},
	{0x54, 0x80},
	{0x55, 0x80},
	{0x56, 0x82},
	{0x8b, 0x40},
	{0x8c, 0x40},
	{0x8d, 0x40},
	{0x8e, 0x2e},
	{0x8f, 0x2e},
	{0x90, 0x2e},
	{0x91, 0x3c},
	{0x92, 0x50},
	{0x5d, 0x12},
	{0x5e, 0x1a},
	{0x5f, 0x24},
	{0x60, 0x07},
	{0x61, 0x15},
	{0x62, 0x08},
	{0x64, 0x03},
	{0x66, 0xe8},
	{0x67, 0x86},
	{0x68, 0xa2},
	{0x69, 0x18},
	{0x6a, 0x0f},
	{0x6b, 0x00},
	{0x6c, 0x5f},
	{0x6d, 0x8f},
	{0x6e, 0x55},
	{0x6f, 0x38},
	{0x70, 0x15},
	{0x71, 0x33},
	{0x72, 0xdc},
	{0x73, 0x80},
	{0x74, 0x02},
	{0x75, 0x3f},
	{0x76, 0x02},
	{0x77, 0x36},
	{0x78, 0x88},
	{0x79, 0x81},
	{0x7a, 0x81},
	{0x7b, 0x22},
	{0x7c, 0xff},
	{0x93, 0x48},
	{0x94, 0x00},
	{0x95, 0x05},
	{0x96, 0xe8},
	{0x97, 0x40},
	{0x98, 0xf0},
	{0xb1, 0x38},
	{0xb2, 0x38},
	{0xbd, 0x38},
	{0xbe, 0x36},
	{0xd0, 0xc9},
	{0xd1, 0x10},
	{0xd3, 0x80},
	{0xd5, 0xf2},
	{0xd6, 0x16},
	{0xdb, 0x92},
	{0xdc, 0xa5},
	{0xdf, 0x23},
	{0xd9, 0x00},
	{0xda, 0x00},
	{0xe0, 0x09},

	{0xed, 0x04},
	{0xee, 0xa0},
	{0xef, 0x40},
	{0x80, 0x03},
	{0x80, 0x03},
	{0x9F, 0x10},
	{0xA0, 0x20},
	{0xA1, 0x38},
	{0xA2, 0x4E},
	{0xA3, 0x63},
	{0xA4, 0x76},
	{0xA5, 0x87},
	{0xA6, 0xA2},
	{0xA7, 0xB8},
	{0xA8, 0xCA},
	{0xA9, 0xD8},
	{0xAA, 0xE3},
	{0xAB, 0xEB},
	{0xAC, 0xF0},
	{0xAD, 0xF8},
	{0xAE, 0xFD},
	{0xAF, 0xFF},
	{0xc0, 0x00},
	{0xc1, 0x10},
	{0xc2, 0x1C},
	{0xc3, 0x30},
	{0xc4, 0x43},
	{0xc5, 0x54},
	{0xc6, 0x65},
	{0xc7, 0x75},
	{0xc8, 0x93},
	{0xc9, 0xB0},
	{0xca, 0xCB},
	{0xcb, 0xE6},
	{0xcc, 0xFF},
	{0xf0, 0x02},
	{0xf1, 0x01},
	{0xf2, 0x01},
	{0xf3, 0x30},
	{0xf9, 0x9f},
	{0xfa, 0x78},

	{0xfe, 0x01},/* set page1 */
	{0x00, 0xf5},
	{0x02, 0x1a},
	{0x0a, 0xa0},
	{0x0b, 0x60},
	{0x0c, 0x08},
	{0x0e, 0x4c},
	{0x0f, 0x39},
	{0x11, 0x3f},
	{0x12, 0x72},
	{0x13, 0x13},
	{0x14, 0x42},
	{0x15, 0x43},
	{0x16, 0xc2},
	{0x17, 0xa8},
	{0x18, 0x18},
	{0x19, 0x40},
	{0x1a, 0xd0},
	{0x1b, 0xf5},
	{0x70, 0x40},
	{0x71, 0x58},
	{0x72, 0x30},
	{0x73, 0x48},
	{0x74, 0x20},
	{0x75, 0x60},
	{0x77, 0x20},
	{0x78, 0x32},
	{0x30, 0x03},
	{0x31, 0x40},
	{0x32, 0xe0},
	{0x33, 0xe0},
	{0x34, 0xe0},
	{0x35, 0xb0},
	{0x36, 0xc0},
	{0x37, 0xc0},
	{0x38, 0x04},
	{0x39, 0x09},
	{0x3a, 0x12},
	{0x3b, 0x1C},
	{0x3c, 0x28},
	{0x3d, 0x31},
	{0x3e, 0x44},
	{0x3f, 0x57},
	{0x40, 0x6C},
	{0x41, 0x81},
	{0x42, 0x94},
	{0x43, 0xA7},
	{0x44, 0xB8},
	{0x45, 0xD6},
	{0x46, 0xEE},
	{0x47, 0x0d},

	{0xfe, 0x00}, /* set page0 */
	{0xd2, 0x90},


	/*-----------Update the registers 2010/07/06-------------*/
	/* Registers of Page0 */
	{0xfe, 0x00}, /* set page0 */
	{0x10, 0x26},
	{0x11, 0x0d},  /* fd,modified by mormo 2010/07/06 */
	{0x1a, 0x2a},  /* 1e,modified by mormo 2010/07/06 */

	{0x1c, 0x49}, /* c1,modified by mormo 2010/07/06 */
	{0x1d, 0x9a}, /* 08,modified by mormo 2010/07/06 */
	{0x1e, 0x61}, /* 60,modified by mormo 2010/07/06 */

	{0x3a, 0x20},

	{0x50, 0x14},  /* 10,modified by mormo 2010/07/06 */
	{0x53, 0x80},
	{0x56, 0x80},

	{0x8b, 0x20}, /* LSC */
	{0x8c, 0x20},
	{0x8d, 0x20},
	{0x8e, 0x14},
	{0x8f, 0x10},
	{0x90, 0x14},

	{0x94, 0x02},
	{0x95, 0x07},
	{0x96, 0xe0},

	{0xb1, 0x40}, /* YCPT */
	{0xb2, 0x40},
	{0xb3, 0x40},
	{0xb6, 0xe0},

	{0xd0, 0xcb}, /* AECT  c9,modifed by mormo 2010/07/06 */
	{0xd3, 0x48}, /* 80,modified by mormor 2010/07/06 */

	{0xf2, 0x02},
	{0xf7, 0x12},
	{0xf8, 0x0a},

	/* Registers of Page1 */
	{0xfe, 0x01},/* set page1 */
	{0x02, 0x20},
	{0x04, 0x10},
	{0x05, 0x08},
	{0x06, 0x20},
	{0x08, 0x0a},

	{0x0e, 0x44},
	{0x0f, 0x32},
	{0x10, 0x41},
	{0x11, 0x37},
	{0x12, 0x22},
	{0x13, 0x19},
	{0x14, 0x44},
	{0x15, 0x44},

	{0x19, 0x50},
	{0x1a, 0xd8},

	{0x32, 0x10},

	{0x35, 0x00},
	{0x36, 0x80},
	{0x37, 0x00},
	/*-----------Update the registers end---------*/
	{0xfe, 0x00},/* set page0 */

	/*-----------GAMMA Select(2)---------------*/
	{0x9F, 0x0E},
	{0xA0, 0x1C},
	{0xA1, 0x34},
	{0xA2, 0x48},
	{0xA3, 0x5A},
	{0xA4, 0x6B},
	{0xA5, 0x7B},
	{0xA6, 0x95},
	{0xA7, 0xAB},
	{0xA8, 0xBF},
	{0xA9, 0xCE},
	{0xAA, 0xD9},
	{0xAB, 0xE4},
	{0xAC, 0xEC},
	{0xAD, 0xF7},
	{0xAE, 0xFD},
	{0xAF, 0xFF},

	/*-----------GAMMA Select End--------------*/

	/*-------------H_V_Switch(4)---------------*/
	{0xfe, 0x00},
	{0x14, 0x13},

	{0xff, 0xff},
};

static struct cc_power_act power_up_act[] = {
	{CC_PWDN, CC_DOWN},
	{CC_DELAY, 10},
	{CC_RESET, CC_DOWN},
	{CC_DELAY, 10},
	{CC_RESET, CC_UP},
	{CC_DELAY, 50},
};

static struct cc_power_act power_down_act[] = {
	{CC_RESET, CC_DOWN},
	{CC_DELAY, 5},
};

#define FHD_WIDTH		640
#define FHD_HEIGHT		480

static const struct cc_frame_cfg frame_cfgs[] = {
	{
		.width = FHD_WIDTH,
		.height = FHD_HEIGHT,
		.framerate = { 1, 30 }, /* 30fps */
		.reg_cfgs[0].regs = init_reg_list,
		.reg_cfgs[0].regs_cnt = ARRAY_SIZE(init_reg_list),
		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
		.colorspace = V4L2_COLORSPACE_JPEG,
		.field = V4L2_FIELD_NONE,
	},
};

static int sensor_detect(struct cc_sensor_info *cc_si)
{
	int ret = 0;
	u8 id;

	ret = cc_si->read_reg(cc_si, 0x00, &id);
	if (ret != 0) {
		PRT_ERR("id=%x\n", id);
		return -ENODEV;
	}

	if (id != 0x9b) {
		PRT_ERR("ID wrong! (0x%02x != 0x9d)\n", id);
		return -ENODEV;
	}

	return 0;
}

static int sensor_init(struct cc_sensor_info *cc_si)
{
	struct timespec ts_s;
	struct timespec ts_f;
	int ret = 0;

	PRT_INFO("\n");

	ret = sensor_detect(cc_si);
	if (ret != 0)
		return ret;

	ktime_get_ts(&ts_s);

	ktime_get_ts(&ts_f);

	PRT_INFO("write %s init_reg_list finish.\n", cc_si->name);
	PRT_INFO("timeused %d %d\n", (int)(ts_f.tv_sec - ts_s.tv_sec),
		(int)(ts_f.tv_nsec - ts_s.tv_nsec));

	return 0;
}

static int sensor_set_stream(struct cc_sensor_info *cc_si, int on)
{
	int ret = 0;

	PRT_INFO("*************************on:%d\n", on);

	if (on) {
		ret = cc_si->write_reg_list(cc_si, init_reg_list,
						ARRAY_SIZE(init_reg_list));
		if (ret != 0) {
			PRT_ERR("write %s init_reg_list failed!\n",
				cc_si->name);
			return -ENODEV;
		}
	}

	return ret;
}

/*
 * ctrl for sensor start
 */
static int gc0308_set_ae_target(struct cc_sensor_info *cc_si, int val)
{
	int ret = 0;

	return ret;
}

static long gc0308_set_ae_meter(struct cc_sensor_info *cc_si, void *arg)
{
	long ret = 0;

	return ret;
}

static int gc0308_get_exif(struct cc_sensor_info *cc_si,
			struct viss_isp_exif *exif)
{
	int ret = 0;
	return ret;
}

/*
 * V4L2 subdev internal operations
 */
static long sensor_ioctl(struct cc_sensor_info *cc_si, unsigned int cmd,
			void *arg)
{
	long ret = 0;

	switch (cmd) {
	case VIDIOC_ISP_SET_AE_METER:
		ret = gc0308_set_ae_meter(cc_si, arg);
		break;
	case VIDIOC_ISP_SET_AE_TARGET:
		gc0308_set_ae_target(cc_si, *(unsigned int *) arg);
		break;
	case VIDIOC_VISS_ISP_EXIF_REQ:
		gc0308_get_exif(cc_si, (struct viss_isp_exif *)arg);
		break;
	default:
		return -EINVAL;
	}
	return ret;
}

#define DRIVER_NAME "gc0308_dvp"

static int sensor_probe(struct platform_device *pdev)
{
	struct cc_sensor_info *cc_si = NULL;
	int ret = 0;

	PRT_INFO("*********probe begin\n");

	cc_si = devm_kzalloc(&pdev->dev, sizeof(*cc_si), GFP_KERNEL);
	if (cc_si == NULL) {
		PRT_ERR("Can't alloc memory!\n");
		return -ENOMEM;
	}

	cc_si->reg_bits = 8;
	cc_si->val_bits = 8;

	cc_si->name = DRIVER_NAME;

	cc_si->power_up_act = power_up_act;
	cc_si->power_down_act = power_down_act;
	cc_si->npwr_up_acts = ARRAY_SIZE(power_up_act);
	cc_si->npwr_dn_acts = ARRAY_SIZE(power_down_act);

	cc_si->framecfgs = frame_cfgs;
	cc_si->nfrmcfgs = ARRAY_SIZE(frame_cfgs);

	ret = cc_sensor_probe(pdev, cc_si);
	if (ret < 0)
		return ret;

	cc_si->detect = sensor_detect;
	cc_si->sensor_init = sensor_init;
	cc_si->set_stream = sensor_set_stream;
	cc_si->sensor_ioctl = sensor_ioctl;

	PRT_INFO("probe end\n");
	return 0;
}

static int sensor_remove(struct platform_device *pdev)
{
	struct cc_sensor_info *cc_si = platform_get_drvdata(pdev);

	cc_sensor_remove(pdev);

	devm_kfree(&pdev->dev, cc_si);

	return 0;
}

static const struct of_device_id gc0308_dvp_of_match[] = {
	{
		.compatible = DRIVER_NAME,
	},
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, gc0308_dvp_of_match);

static struct platform_driver gc0308_dvp_driver = {
	.driver = {
		.of_match_table	= gc0308_dvp_of_match,
		.name		= DRIVER_NAME,
		.owner		= THIS_MODULE,
	},
	.probe = sensor_probe,
	.remove = sensor_remove,
};

module_platform_driver(gc0308_dvp_driver);

MODULE_DESCRIPTION("GC0308-DVP sensor driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);

